/* -*- mode: c++; coding: utf-8-unix -*-
 */

#include <stdlib.h>
#include <iostream>
#include <string>
#include <glib.h>
#include <exception>
#include <assert.h>

#include "Algorithm.h"
#include "Image.h"
#include "Configuration.h"
#include "Log.h"
#include <list>
#include <map>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

namespace test
{
	void image(const cfg::Config &conf);
}

typedef std::list<alg::Workunit*> wu_list_type;
typedef std::list<img::Image*> img_list_type;

void process_dft(gpointer data, gpointer user_data);
void process_dct(gpointer data, gpointer user_data);
GFunc createWorkunits(const cfg::Config &conf,
		      wu_list_type &wulist,
		      img_list_type &imglist);

int main(int argc, char **argv)
try
{
	initializeLogging();
	USELOG();

	g_thread_init(0);

	LOG_DEBUG("Parsing command line ");
	cfg::Config conf(argc, argv);

	LOG_DEBUG("Parsed config:\n" << conf.toString());

	wu_list_type wulist;
	img_list_type images;
	GFunc corefunc = createWorkunits(conf, wulist, images);

	LOG_DEBUG("*** Image objects:");
	for (img_list_type::const_iterator i=images.begin(); i!=images.end(); i++)
		LOG_DEBUG(*i);
	LOG_DEBUG("*** Workunits:");
	for (wu_list_type::const_iterator i=wulist.begin(); i!=wulist.end(); i++)
		LOG_DEBUG((*i)->toString());

	LOG_DEBUG("Processing workunits");

	GError *err = 0;
	GThreadPool *pool = g_thread_pool_new(corefunc, 0, conf.threadcount(),
					      TRUE, &err);
	if (err) throw err;

	for (wu_list_type::const_iterator i=wulist.begin(); i!=wulist.end(); i++)
	{
		g_thread_pool_push(pool, *i, &err);
		if (err) throw err;
	}

	LOG_DEBUG("Waiting for threads...");
	g_thread_pool_free(pool, FALSE, TRUE);
	LOG_DEBUG("Threads done. Saving images...");

	for (img_list_type::const_iterator i=images.begin(); i!=images.end(); i++)
		(*i)->save(conf.squeeze());
	
	for (img_list_type::const_iterator i=images.begin(); i!=images.end(); i++)
		delete *i;

	LOG_DEBUG("Exiting");
	return 0;
}
catch (const std::exception &ex)
{
	USELOG();
	LOG_ERROR(ex.what());
	exit(1);
}
catch (GError *err)
{
	USELOG();
	LOG_ERROR("GLib: "
		  << g_quark_to_string(err->domain) << ": ("
		  << err->code << ") " << err->message);
	exit(1);
}
catch (int ex)
{
	exit(ex);
}
catch (const std::string& msg)
{
	std::cerr << "ERROR: " << msg << std::endl;
	exit(1);
}

void process_dft(gpointer data, gpointer user_data)
try
{
	alg::DFT_Workunit *wu = reinterpret_cast<alg::DFT_Workunit*>(data);
	dft(*wu);
}
catch (const std::string &s)
{
	USELOG();
	LOG_ERROR(s);
}
void process_dct(gpointer data, gpointer user_data)
try
{
	alg::DCT_Workunit *wu = reinterpret_cast<alg::DCT_Workunit*>(data);
	dct(*wu);
}
catch (const std::string &s)
{
	USELOG();
	LOG_ERROR(s);
}
img::Image *openImage(bool grayscale, const std::string &name,
		 img::size_type *w, img::size_type *h)
{
	img::Image *img = new img::Image(grayscale, name);
	*w = img->width();
	*h = img->height();
	return img;
}
img::Channel imageChannel(cfg::ChannelSpec cs)
{
	switch (cs.channel)
	{
	case cfg::Red: return img::Red;
	case cfg::Green: return img::Green;
	case cfg::Blue: return img::Blue;
	case cfg::Gray: return img::Gray;
	}
}
GFunc createWorkunits(const cfg::Config &conf,
		      wu_list_type &wulist,
		      img_list_type &imglist)
{
	typedef std::map<cfg::ChannelSpec, img::Image*> gray_imgs_type;
	typedef std::map<cfg::Channel, alg::Workunit*> wu_map;

	img::Image *inputimg = 0, *grayinputimg = 0, *mergedimg = 0;
	gray_imgs_type outimg;
	img::size_type w, h;
	img::size_type ow = conf.outWidth();
	img::size_type oh = conf.outHeight();
	wu_map wu;

	cfg::OutputType outtype = conf.outputType();
	bool mergedout = (outtype == cfg::Merged);
	bool isdft = (conf.mode() == cfg::DFT);

	img::Channel destChannel = img::Red;

	for (cfg::chlist_citerator i = conf.selectedChannels().begin();
	     i != conf.selectedChannels().end(); i++)
	{
		cfg::ChannelSpec cs = *i;
		bool grayInput = (cs.channel == cfg::Gray);

		if (grayInput && !grayinputimg)
			grayinputimg = openImage(true, conf.infile(), &w, &h);
		if (!grayInput && !inputimg)
			inputimg = openImage(false, conf.infile(), &w, &h);

		if (!ow) ow = w;
		if (!oh) oh = h;

		if (mergedout && !mergedimg)
			mergedimg = new img::Image(false, ow, oh, conf.outfile());
		if (!mergedout && !outimg[cs])
			outimg[cs] =
				new img::Image(true, ow, oh, conf.outfile(cs));

		if (!wu[cs.channel])
		{
			img::Image *inimg = grayInput ? grayinputimg : inputimg;
			alg::ChDef cd(inimg, imageChannel(cs));
			wu[cs.channel] = alg::Workunit::create(isdft, conf.center(), cd);
		}

		alg::ChDef dst;
		if (mergedout)
			dst = alg::ChDef(mergedimg, destChannel++);
		else
			dst = alg::ChDef(outimg[cs], img::Gray);


		if (isdft)
		{
			alg::DFT_Workunit *w =
				dynamic_cast<alg::DFT_Workunit*>(wu[cs.channel]);
			switch (cs.subchannel)
			{
			case cfg::Imaginary: w->imagDest(dst); break;
			case cfg::Real: w->realDest(dst); break;
			case cfg::Magnitude: w->magnitudeDest(dst); break;
			case cfg::Phase: w->phaseDest(dst); break;
			default:
				assert(false); break;
			}
		}
		else
		{
			alg::DCT_Workunit *w =
				dynamic_cast<alg::DCT_Workunit*>(wu[cs.channel]);
			w->dest(dst);
		}
	}

	img::Image *m[] = {inputimg, grayinputimg, mergedimg};
	for (int i=0; i<3; i++)
		if (m[i]) imglist.push_back(m[i]);
	for (gray_imgs_type::iterator i = outimg.begin();
	     i != outimg.end(); i++)
		if (i->second) imglist.push_back(i->second);
	for (wu_map::iterator i=wu.begin(); i!=wu.end(); i++)
	{
		const img::size_type frame_size = conf.framesize();
		for (img::size_type ln=0; ln<oh; ln+=frame_size)
		{
			alg::Workunit* nwu;
			if (isdft)
				nwu = new alg::DFT_Workunit(
					*dynamic_cast<alg::DFT_Workunit*>(i->second));
			else
				nwu = new alg::DCT_Workunit(
					*dynamic_cast<alg::DCT_Workunit*>(i->second));

			img::size_type linesleft = oh-ln;
			nwu->range(ln, frame_size < linesleft ? frame_size : linesleft);
			wulist.push_back(nwu);
		}
	}

	if (isdft) return process_dft;
	else return process_dct;
}
